/* Copyright © 2023 - 2024 Coremail论客
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import util from '@ohos.util';
import type {
  IBuffer,
  IBufferCreator,
} from "../api";
import { findBuffer, findBufferMost } from "./buffer_utils"
import { createBuffer, memBufferCreator } from "./file_stream";
import { CMError, ErrorCode } from "../api";
import { getLogger } from "./log";
import { decodeCharset } from './encodings';

const logger = getLogger("data_reader");

export interface IReader {
  /*
   * 读取内容，直到遇到`end`的值。
   * 返回的Promise，成功的话resolve一个字符串，不包括`end`的值。失败则reject错误
   * 移动当前位置到`end`的后一个位置
   */
  readUntil(end: string): Promise<string>;
  /*
   * 读取内容，直到遇到`end`的值。
   * 返回的Promise，成功的话resolve一个IBuffer对象，不包括`end`的值。失败则reject错误
   * 用于读取附件、内联附件内容这类大数据的内容
   * 移动当前位置到`end`的后一个位置
   */
  readBlockUntil(end: string | Uint8Array): Promise<IBuffer>;
  readToEnd(): Promise<IBuffer>;
  /*
   * 获取当前`n`个字符串，不移动当前位置。
   */
  peek(n: number): Promise<string>;
  /*
   * 跳过空白字符，空白字符是指空格，制表符，换行，回车这四个字符
   * 移动当前位置到空白字符的后一个位置
   */
  skipSpace(): Promise<void>;
  /*
   * 跳过`n`个字符
   * 移动当前位置到跳过的字符的后一个位置
   */
  skip(n: number): Promise<void>;
  /*
   * 判断是否结束
   */
  end(): Promise<boolean>;
}

export class StringReader implements IReader {
  _data: string;
  _offset: number = 0;
  constructor(s: string) {
    this._data = s;
  }
  readUntil(end: string): Promise<string> {
    if (!end) {
      return Promise.reject("parameter error");
    }
    const idx = this._data.indexOf(end, this._offset);
    if (idx < 0) {
      return Promise.reject("not found");
    }
    const result = this._data.substring(this._offset, idx);
    this._offset = idx + end.length;
    return Promise.resolve(result);
  }
  async readBlockUntil(end: string | Uint8Array): Promise<IBuffer> {
    if (typeof end != 'string') {
      end = util.TextDecoder.create('utf-8').decodeWithStream(end);
    }
    const buff = memBufferCreator.createBuffer({});
    buff.end(await this.readUntil(end));
    return buff;
  }
  readToEnd(): Promise<IBuffer> {
    const buff = memBufferCreator.createBuffer({});
    buff.end(this._data.substring(this._offset));
    this._offset = this._data.length;
    return Promise.resolve(buff);
  }

  peek(n: number): Promise<string> {
    return Promise.resolve(this._data.substring(this._offset, this._offset + n));
  }

  skipSpace(): Promise<void> {
    let i = this._offset;
    for (; i < this._data.length; i++) {
      const ch = this._data[i];
      if (!(ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')) {
        break;
      }
    }
    this._offset = i;
    return Promise.resolve()
  }

  skip(n: number): Promise<void> {
    this._offset += n;
    return Promise.resolve()
  }
  end(): Promise<boolean> {
    const finished = this._offset >= this._data.length;
    return Promise.resolve(finished);
  }
}

export class BufferReader implements IReader {
  _bufferCreator: IBufferCreator;
  _currentBuffer: Uint8Array = new Uint8Array;
  _offset: number = 0;
  _bufferIterator: AsyncIterator<Uint8Array>;

  constructor(bufferIterator: AsyncIterator<Uint8Array>, bufferCreator?: IBufferCreator) {
    this._bufferCreator = bufferCreator || memBufferCreator;
    this._bufferIterator = bufferIterator;
  }

  /*
  尝试去拿数据，返回是否有新数据
   */
  async fetchData(): Promise<boolean> {
    // logger.debug('get data start', this._currentBuffer.byteLength, this._offset);
    const result = await this._bufferIterator.next();
    if (result.done) {
      return false;
    }
    const newBuffer: Uint8Array = result.value;
    if (this._offset < this._currentBuffer.byteLength) {
      const remain = this._currentBuffer.byteLength - this._offset;
      const buff = new Uint8Array(remain + newBuffer.byteLength);
      buff.set(this._currentBuffer.subarray(this._offset));
      buff.set(newBuffer, remain);
      this._currentBuffer = buff;
    } else {
      const buff = new Uint8Array(newBuffer.byteLength);
      buff.set(newBuffer);
      this._currentBuffer = buff;
    }
    this._offset = 0;
    // logger.debug('get data end', this._currentBuffer.byteLength);
    return true;
  }

  /*
   读取内容，直到遇到`end`的值。如果`end`为空，则读到Buffer的末尾
   返回的Promise，成功的话resolve一个字符串，不包括`end`的值。失败则reject错误
   底层是二进制，默认按照**UTF-8**解码
  */
  async readUntil(end: string): Promise<string> {
    let target: Uint8Array = (new util.TextEncoder()).encodeInto(end);
    let idx = findBuffer(this._currentBuffer, target, this._offset);
    while (idx < 0) {
      const hasNew = await this.fetchData();
      if (!hasNew) {
        logger.error('buffer reader: on data')
        throw new CMError('not found');
      }
      idx = findBuffer(this._currentBuffer, target, this._offset);
    }
    const buffer = this._currentBuffer.subarray(this._offset, idx);
    let result = '';
    if (buffer.byteLength > 0) {
      result = decodeCharset(buffer);
    }

    this._offset = idx + target.byteLength;
    if (this._offset >= this._currentBuffer.byteLength) {
      await this.fetchData();
    }
    return result;
  }

  /*
   读取内容，直到遇到`end`的值，或者读到`Buffer`的末尾。
   返回的Promise，成功的话是一个IBuffer对象，不包括`end`的值。
   */
  async readBlockUntil(end: string | Uint8Array): Promise<IBuffer> {
    if (typeof end == 'string') {
      end = (new util.TextEncoder()).encodeInto(end);
    }
    const buff = this._bufferCreator.createBuffer({useFile: false});
    this._currentBuffer = this._currentBuffer.subarray(this._offset);
    this._offset = 0;
    let idx = -1;
    while (true) {
      idx = findBuffer(this._currentBuffer, end);
      if (idx < 0) {
        let partialEqual = false;
        let remain: Uint8Array;
        const firstCharLoc = findBufferMost(this._currentBuffer, end);
        if (firstCharLoc + end.byteLength > this._currentBuffer.byteLength) {
          remain = this._currentBuffer.subarray(firstCharLoc);
          partialEqual = true
        }
        if (partialEqual) {
          buff.feed(this._currentBuffer.subarray(0, firstCharLoc));
          this._currentBuffer = remain;
          this._offset = 0;
        } else {
          buff.feed(this._currentBuffer);
          this._currentBuffer = new Uint8Array();
          this._offset = 0;
        }

        const hasNew = await this.fetchData();
        if (!hasNew) {
          buff.end()
          break;
          // throw new CMError('not found');
        }
      } else {
        const b = this._currentBuffer.subarray(0, idx);
        buff.end(b)
        this._currentBuffer = this._currentBuffer.subarray(idx + end.byteLength);
        this._offset = 0;
        break;
      }
    }
    await this.fetchData();
    return buff;
  }

  async readToEnd(): Promise<IBuffer> {
    const buff = this._bufferCreator.createBuffer({useFile: false});
    let buffer = this._currentBuffer.subarray(this._offset);
    this._offset = this._currentBuffer.byteLength;
    if (buffer.byteLength > 0) {
      buff.feed(buffer)
    }
    while (await this.fetchData()) {
      buff.feed(this._currentBuffer);
      this._offset = this._currentBuffer.byteLength;
    }
    buff.end();
    return buff;
  }

  async peek(n: number = 1): Promise<string> { // 只处理 ascii 字符
    if (this._offset + n >= this._currentBuffer.byteLength) {
      await this.fetchData();
    }
    const b = this._currentBuffer.subarray(this._offset, this._offset + n);
    const s = Array.from(b).map(n => String.fromCharCode(n)).join('');
    return s;
  }
  /*
    跳过空白字符，包括空格' '，制表符'\t'，换行'\n'，回车'\r'等
   */
  async skipSpace(): Promise<void> {
    let i = 0;
    while (true) {
      if (this._offset >= this._currentBuffer.byteLength) {
        const hasNew = await this.fetchData();
        if (!hasNew) {
          break;
        }
      }
      const ch = this._currentBuffer[this._offset];
      if (!(ch == 32 || ch == 9 || ch == 13 || ch == 10)) {
        break;
      }
      this._offset += 1;
    }
  }

  async skip(n: number = 1): Promise<void> {
    this._offset += n;
    if (this._offset >= this._currentBuffer.byteLength) {
      await this.fetchData();
    }
  }

  async end(): Promise<boolean> {
    if (this._offset >= this._currentBuffer.byteLength) {
      const hasMoreData = await this.fetchData();
      if (hasMoreData) {
        return false;
      }
    }
    return this._offset >= this._currentBuffer.byteLength;
  }
}

